const {
    HookMap,
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
} = require('tapable');

class Compilation {
    /**
     * Creates an instance of Compilation.
     * @param {Compiler} compiler the compiler which created the compilation
     */
    constructor(compiler) {
        /** @type {AsyncSeriesHook<[CompilationAssets]>} */
        const processAssetsHook = new AsyncSeriesHook(['assets']);
        /** @type {SyncHook<[CompilationAssets]>} */
        const afterProcessAssetsHook = new SyncHook(['assets']);
        this.hooks = Object.freeze({
            /** @type {SyncHook<[Module]>} */
            buildModule: new SyncHook(['module']),
            /** @type {SyncHook<[Module]>} */
            rebuildModule: new SyncHook(['module']),
            /** @type {SyncHook<[Module, WebpackError]>} */
            failedModule: new SyncHook(['module', 'error']),
            /** @type {SyncHook<[Module]>} */
            succeedModule: new SyncHook(['module']),
            /** @type {SyncHook<[Module]>} */
            stillValidModule: new SyncHook(['module']),

            /** @type {SyncHook<[Dependency, EntryOptions]>} */
            addEntry: new SyncHook(['entry', 'options']),
            /** @type {SyncHook<[Dependency, EntryOptions, Error]>} */
            failedEntry: new SyncHook(['entry', 'options', 'error']),
            /** @type {SyncHook<[Dependency, EntryOptions, Module]>} */
            succeedEntry: new SyncHook(['entry', 'options', 'module']),

            /** @type {SyncWaterfallHook<[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]>} */
            dependencyReferencedExports: new SyncWaterfallHook([
                'referencedExports',
                'dependency',
                'runtime',
            ]),

            /** @type {AsyncSeriesHook<[Iterable<Module>]>} */
            finishModules: new AsyncSeriesHook(['modules']),
            /** @type {AsyncSeriesHook<[Module]>} */
            finishRebuildingModule: new AsyncSeriesHook(['module']),
            /** @type {SyncHook<[]>} */
            unseal: new SyncHook([]),
            /** @type {SyncHook<[]>} */
            seal: new SyncHook([]),

            /** @type {SyncHook<[]>} */
            beforeChunks: new SyncHook([]),
            /** @type {SyncHook<[Iterable<Chunk>]>} */
            afterChunks: new SyncHook(['chunks']),

            /** @type {SyncBailHook<[Iterable<Module>]>} */
            optimizeDependencies: new SyncBailHook(['modules']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            afterOptimizeDependencies: new SyncHook(['modules']),

            /** @type {SyncHook<[]>} */
            optimize: new SyncHook([]),
            /** @type {SyncBailHook<[Iterable<Module>]>} */
            optimizeModules: new SyncBailHook(['modules']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            afterOptimizeModules: new SyncHook(['modules']),

            /** @type {SyncBailHook<[Iterable<Chunk>, ChunkGroup[]]>} */
            optimizeChunks: new SyncBailHook(['chunks', 'chunkGroups']),
            /** @type {SyncHook<[Iterable<Chunk>, ChunkGroup[]]>} */
            afterOptimizeChunks: new SyncHook(['chunks', 'chunkGroups']),

            /** @type {AsyncSeriesHook<[Iterable<Chunk>, Iterable<Module>]>} */
            optimizeTree: new AsyncSeriesHook(['chunks', 'modules']),
            /** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */
            afterOptimizeTree: new SyncHook(['chunks', 'modules']),

            /** @type {AsyncSeriesBailHook<[Iterable<Chunk>, Iterable<Module>]>} */
            optimizeChunkModules: new AsyncSeriesBailHook(['chunks', 'modules']),
            /** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */
            afterOptimizeChunkModules: new SyncHook(['chunks', 'modules']),
            /** @type {SyncBailHook<[], boolean>} */
            shouldRecord: new SyncBailHook([]),

            /** @type {SyncHook<[Chunk, Set<string>]>} */
            additionalChunkRuntimeRequirements: new SyncHook(['chunk', 'runtimeRequirements']),
            /** @type {HookMap<SyncBailHook<[Chunk, Set<string>]>>} */
            runtimeRequirementInChunk: new HookMap(
                () => new SyncBailHook(['chunk', 'runtimeRequirements'])
            ),
            /** @type {SyncHook<[Module, Set<string>]>} */
            additionalModuleRuntimeRequirements: new SyncHook(['module', 'runtimeRequirements']),
            /** @type {HookMap<SyncBailHook<[Module, Set<string>]>>} */
            runtimeRequirementInModule: new HookMap(
                () => new SyncBailHook(['module', 'runtimeRequirements'])
            ),
            /** @type {SyncHook<[Chunk, Set<string>]>} */
            additionalTreeRuntimeRequirements: new SyncHook(['chunk', 'runtimeRequirements']),
            /** @type {HookMap<SyncBailHook<[Chunk, Set<string>]>>} */
            runtimeRequirementInTree: new HookMap(
                () => new SyncBailHook(['chunk', 'runtimeRequirements'])
            ),

            /** @type {SyncHook<[RuntimeModule, Chunk]>} */
            runtimeModule: new SyncHook(['module', 'chunk']),

            /** @type {SyncHook<[Iterable<Module>, any]>} */
            reviveModules: new SyncHook(['modules', 'records']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            beforeModuleIds: new SyncHook(['modules']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            moduleIds: new SyncHook(['modules']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            optimizeModuleIds: new SyncHook(['modules']),
            /** @type {SyncHook<[Iterable<Module>]>} */
            afterOptimizeModuleIds: new SyncHook(['modules']),

            /** @type {SyncHook<[Iterable<Chunk>, any]>} */
            reviveChunks: new SyncHook(['chunks', 'records']),
            /** @type {SyncHook<[Iterable<Chunk>]>} */
            beforeChunkIds: new SyncHook(['chunks']),
            /** @type {SyncHook<[Iterable<Chunk>]>} */
            chunkIds: new SyncHook(['chunks']),
            /** @type {SyncHook<[Iterable<Chunk>]>} */
            optimizeChunkIds: new SyncHook(['chunks']),
            /** @type {SyncHook<[Iterable<Chunk>]>} */
            afterOptimizeChunkIds: new SyncHook(['chunks']),

            /** @type {SyncHook<[Iterable<Module>, any]>} */
            recordModules: new SyncHook(['modules', 'records']),
            /** @type {SyncHook<[Iterable<Chunk>, any]>} */
            recordChunks: new SyncHook(['chunks', 'records']),

            /** @type {SyncHook<[Iterable<Module>]>} */
            optimizeCodeGeneration: new SyncHook(['modules']),

            /** @type {SyncHook<[]>} */
            beforeModuleHash: new SyncHook([]),
            /** @type {SyncHook<[]>} */
            afterModuleHash: new SyncHook([]),

            /** @type {SyncHook<[]>} */
            beforeCodeGeneration: new SyncHook([]),
            /** @type {SyncHook<[]>} */
            afterCodeGeneration: new SyncHook([]),

            /** @type {SyncHook<[]>} */
            beforeRuntimeRequirements: new SyncHook([]),
            /** @type {SyncHook<[]>} */
            afterRuntimeRequirements: new SyncHook([]),

            /** @type {SyncHook<[]>} */
            beforeHash: new SyncHook([]),
            /** @type {SyncHook<[Chunk]>} */
            contentHash: new SyncHook(['chunk']),
            /** @type {SyncHook<[]>} */
            afterHash: new SyncHook([]),
            /** @type {SyncHook<[any]>} */
            recordHash: new SyncHook(['records']),
            /** @type {SyncHook<[Compilation, any]>} */
            record: new SyncHook(['compilation', 'records']),

            /** @type {SyncHook<[]>} */
            beforeModuleAssets: new SyncHook([]),
            /** @type {SyncBailHook<[], boolean>} */
            shouldGenerateChunkAssets: new SyncBailHook([]),
            /** @type {SyncHook<[]>} */
            beforeChunkAssets: new SyncHook([]),

            processAssets: processAssetsHook,
            afterProcessAssets: afterProcessAssetsHook,

            /** @type {SyncBailHook<[], boolean>} */
            needAdditionalSeal: new SyncBailHook([]),
            /** @type {AsyncSeriesHook<[]>} */
            afterSeal: new AsyncSeriesHook([]),

            /** @type {SyncWaterfallHook<[RenderManifestEntry[], RenderManifestOptions]>} */
            renderManifest: new SyncWaterfallHook(['result', 'options']),

            /** @type {SyncHook<[Hash]>} */
            fullHash: new SyncHook(['hash']),
            /** @type {SyncHook<[Chunk, Hash, ChunkHashContext]>} */
            chunkHash: new SyncHook(['chunk', 'chunkHash', 'ChunkHashContext']),

            /** @type {SyncHook<[Module, string]>} */
            moduleAsset: new SyncHook(['module', 'filename']),
            /** @type {SyncHook<[Chunk, string]>} */
            chunkAsset: new SyncHook(['chunk', 'filename']),

            /** @type {SyncWaterfallHook<[string, object, AssetInfo]>} */
            assetPath: new SyncWaterfallHook(['path', 'options', 'assetInfo']),

            /** @type {SyncBailHook<[], boolean>} */
            needAdditionalPass: new SyncBailHook([]),

            /** @type {SyncHook<[Compiler, string, number]>} */
            childCompiler: new SyncHook(['childCompiler', 'compilerName', 'compilerIndex']),

            /** @type {SyncBailHook<[string, LogEntry], true>} */
            log: new SyncBailHook(['origin', 'logEntry']),

            /** @type {SyncWaterfallHook<[WebpackError[]]>} */
            processWarnings: new SyncWaterfallHook(['warnings']),
            /** @type {SyncWaterfallHook<[WebpackError[]]>} */
            processErrors: new SyncWaterfallHook(['errors']),

            /** @type {HookMap<SyncHook<[Object, Object]>>} */
            statsPreset: new HookMap(() => new SyncHook(['options', 'context'])),
            /** @type {SyncHook<[Object, Object]>} */
            statsNormalize: new SyncHook(['options', 'context']),
            /** @type {SyncHook<[StatsFactory, Object]>} */
            statsFactory: new SyncHook(['statsFactory', 'options']),
            /** @type {SyncHook<[StatsPrinter, Object]>} */
            statsPrinter: new SyncHook(['statsPrinter', 'options']),

            // get normalModuleLoader() {
            //     return getNormalModuleLoader();
            // },
        });
        /** @type {string=} */
        this.name = undefined;
        this.startTime = undefined;
        this.endTime = undefined;
        /** @type {Compiler} */
        this.compiler = compiler;
        this.resolverFactory = compiler.resolverFactory;
        this.inputFileSystem = compiler.inputFileSystem;
        this.fileSystemInfo = new FileSystemInfo(this.inputFileSystem, {
            managedPaths: compiler.managedPaths,
            immutablePaths: compiler.immutablePaths,
            logger: this.getLogger('webpack.FileSystemInfo'),
        });
        if (compiler.fileTimestamps) {
            this.fileSystemInfo.addFileTimestamps(compiler.fileTimestamps);
        }
        if (compiler.contextTimestamps) {
            this.fileSystemInfo.addContextTimestamps(compiler.contextTimestamps);
        }
        this.requestShortener = compiler.requestShortener;
        this.compilerPath = compiler.compilerPath;

        this.logger = this.getLogger('webpack.Compilation');

        const options = compiler.options;
        this.options = options;
        this.outputOptions = options && options.output;
        /** @type {boolean} */
        this.bail = (options && options.bail) || false;
        /** @type {boolean} */
        this.profile = (options && options.profile) || false;

        this.mainTemplate = new MainTemplate(this.outputOptions, this);
        this.chunkTemplate = new ChunkTemplate(this.outputOptions, this);
        this.runtimeTemplate = new RuntimeTemplate(this.outputOptions, this.requestShortener);
        /** @type {{javascript: ModuleTemplate}} */
        this.moduleTemplates = {
            javascript: new ModuleTemplate(this.runtimeTemplate, this),
        };
        Object.defineProperties(this.moduleTemplates, {
            asset: {
                enumerable: false,
                configurable: false,
                get() {
                    throw new WebpackError('Compilation.moduleTemplates.asset has been removed');
                },
            },
            webassembly: {
                enumerable: false,
                configurable: false,
                get() {
                    throw new WebpackError(
                        'Compilation.moduleTemplates.webassembly has been removed'
                    );
                },
            },
        });

        this.moduleGraph = new ModuleGraph();
        this.chunkGraph = undefined;
        /** @type {CodeGenerationResults} */
        this.codeGenerationResults = undefined;

        /** @type {AsyncQueue<FactorizeModuleOptions, string, Module>} */
        this.factorizeQueue = new AsyncQueue({
            name: 'factorize',
            parallelism: options.parallelism || 100,
            processor: this._factorizeModule.bind(this),
        });
        /** @type {AsyncQueue<Module, string, Module>} */
        this.addModuleQueue = new AsyncQueue({
            name: 'addModule',
            parallelism: options.parallelism || 100,
            getKey: (module) => module.identifier(),
            processor: this._addModule.bind(this),
        });
        /** @type {AsyncQueue<Module, Module, Module>} */
        this.buildQueue = new AsyncQueue({
            name: 'build',
            parallelism: options.parallelism || 100,
            processor: this._buildModule.bind(this),
        });
        /** @type {AsyncQueue<Module, Module, Module>} */
        this.rebuildQueue = new AsyncQueue({
            name: 'rebuild',
            parallelism: options.parallelism || 100,
            processor: this._rebuildModule.bind(this),
        });
        /** @type {AsyncQueue<Module, Module, Module>} */
        this.processDependenciesQueue = new AsyncQueue({
            name: 'processDependencies',
            parallelism: options.parallelism || 100,
            processor: this._processModuleDependencies.bind(this),
        });

        /**
         * Modules in value are building during the build of Module in key.
         * Means value blocking key from finishing.
         * Needed to detect build cycles.
         * @type {WeakMap<Module, Set<Module>>}
         */
        this.creatingModuleDuringBuild = new WeakMap();

        /** @type {Map<string, EntryData>} */
        this.entries = new Map();
        /** @type {EntryData} */
        this.globalEntry = {
            dependencies: [],
            includeDependencies: [],
            options: {
                name: undefined,
            },
        };
        /** @type {Map<string, Entrypoint>} */
        this.entrypoints = new Map();
        /** @type {Entrypoint[]} */
        this.asyncEntrypoints = [];
        /** @type {Set<Chunk>} */
        this.chunks = new Set();
        arrayToSetDeprecation(this.chunks, 'Compilation.chunks');
        /** @type {ChunkGroup[]} */
        this.chunkGroups = [];
        /** @type {Map<string, ChunkGroup>} */
        this.namedChunkGroups = new Map();
        /** @type {Map<string, Chunk>} */
        this.namedChunks = new Map();
        /** @type {Set<Module>} */
        this.modules = new Set();
        arrayToSetDeprecation(this.modules, 'Compilation.modules');
        /** @private @type {Map<string, Module>} */
        this._modules = new Map();
        this.records = null;
        /** @type {string[]} */
        this.additionalChunkAssets = [];
        /** @type {CompilationAssets} */
        this.assets = {};
        /** @type {Map<string, AssetInfo>} */
        this.assetsInfo = new Map();
        /** @type {Map<string, Map<string, Set<string>>>} */
        this._assetsRelatedIn = new Map();
        /** @type {WebpackError[]} */
        this.errors = [];
        /** @type {WebpackError[]} */
        this.warnings = [];
        /** @type {Compilation[]} */
        this.children = [];
        /** @type {Map<string, LogEntry[]>} */
        this.logging = new Map();
        /** @type {Map<DepConstructor, ModuleFactory>} */
        this.dependencyFactories = new Map();
        /** @type {DependencyTemplates} */
        this.dependencyTemplates = new DependencyTemplates();
        this.childrenCounters = {};
        /** @type {Set<number|string>} */
        this.usedChunkIds = null;
        /** @type {Set<number>} */
        this.usedModuleIds = null;
        /** @type {boolean} */
        this.needAdditionalPass = false;
        /** @type {WeakSet<Module>} */
        this.builtModules = new WeakSet();
        /** @type {WeakSet<Module>} */
        this.codeGeneratedModules = new WeakSet();
        /** @private @type {Map<Module, Callback[]>} */
        this._rebuildingModules = new Map();
        /** @type {Set<string>} */
        this.emittedAssets = new Set();
        /** @type {Set<string>} */
        this.comparedForEmitAssets = new Set();
        /** @type {LazySet<string>} */
        this.fileDependencies = new LazySet();
        /** @type {LazySet<string>} */
        this.contextDependencies = new LazySet();
        /** @type {LazySet<string>} */
        this.missingDependencies = new LazySet();
        /** @type {LazySet<string>} */
        this.buildDependencies = new LazySet();
        // TODO webpack 6 remove
        this.compilationDependencies = {
            add: util.deprecate(
                (item) => this.fileDependencies.add(item),
                'Compilation.compilationDependencies is deprecated (used Compilation.fileDependencies instead)',
                'DEP_WEBPACK_COMPILATION_COMPILATION_DEPENDENCIES'
            ),
        };

        this._modulesCache = this.getCache('Compilation/modules');
        this._assetsCache = this.getCache('Compilation/assets');
        this._codeGenerationCache = this.getCache('Compilation/codeGeneration');
    }
}

module.exports = Compilation;
